package org.zoomdev.zoom.common.config;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.filter.pattern.PatternFilterFactory;
import org.zoomdev.zoom.common.io.Io;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * properties或者yml配置
 *
 * @author jzoom
 */
public class ConfigReader {

    private static final Logger logger = LoggerFactory.getLogger(ConfigReader.class);

    private static final Pattern EL_PATTERN = Pattern.compile("\\$\\{([a-zA-Z0-9_\\.-]+)\\}");


    private static final String ENV = "zoom.env";

    /**
     * app config, 管理多个配置文件
     */
    private static final AppConfigReader applicationReader = new AppConfigReader();
    protected final Map<String, Object> data;
    ConfigLoader loader;
    private String configName;

    ConfigReader() {
        /**
         * 注意这个配置理论上在程序运行期间是不能修改的,所以用HashMap
         *
         */
        data = new LinkedHashMap<>();
    }

    /**
     * 获取默认reader
     *
     * @return
     */
    public static AppConfigReader getDefault() {
        return applicationReader;
    }

    /**
     * 解析配置
     *
     * @param value 解析 ${config} 形式的字符串，并解析出变量名称
     * @return
     */
    public Object parseValue(String value) {
        Matcher matcher = EL_PATTERN.matcher(value);
        if (matcher.matches()) {
            value = matcher.group(1);
            return get(value);
        }
        return value;
    }

    /**
     * 返回原来存储的内容
     *
     * @param key
     * @return
     */
    public Object get(String key) {
        return data.get(key);
    }

    public String getString(String key) {
        return Caster.to(data.get(key), String.class);
    }

    public void load(String name) throws IOException {
        load(name, getClass().getClassLoader());
    }

    /**
     * 加载配置
     *
     * @throws IOException
     */
    @SuppressWarnings({"unchecked"})
    public void load(String name, ClassLoader classLoader) throws IOException {
        assert (name != null);

        logger.info("Loading configuration:" + name);

        InputStream is = tryFind(classLoader, name);
        if (is == null) {
            throw new IOException("找不到" + name + "的资源文件");
        }

        try {
            data.putAll(loader.load(is));
        } catch (IOException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZoomException(String.format("配置文件%s加载失败", configName), e);
        } finally {
            Io.close(is);
        }
    }

    /**
     * 获取对应的key
     *
     * @param pattern 模式，类似redis的keys命令 ,  可以用 *\/key*等
     */
    public List<String> keys(String pattern) {
        List<String> keys = new ArrayList<>();
        Filter<String> keyFilter = PatternFilterFactory.createFilter(pattern);
        for (String key : data.keySet()) {
            if (keyFilter.accept(key)) {
                keys.add(key);
            }
        }
        return keys;
    }

    protected InputStream tryFind(ClassLoader classLoader, String name) {
        InputStream is;
        if (name.endsWith(".json")) {
            is = classLoader.getResourceAsStream(name);
            if (is != null) {
                configName = name;
                loader = new JsonConfigReader();
            }
        } else if (name.endsWith(".properties")) {
            is = classLoader.getResourceAsStream(name);
            if (is != null) {
                configName = name;
                loader = new SimpleReader();
            }
        } else {
            is = tryFind(classLoader, name + ".properties");
            if (is == null) {
                is = tryFind(classLoader, name + ".json");
            }
        }
        return is;
    }

    public static class AppConfigReader extends ConfigReader {

        private final Map<String, ConfigReader> map = new ConcurrentHashMap<>();

        /**
         * 尝试获取一个对象
         *
         * @param key
         * @return
         */
        public Object getObject(String key, String file) {
            if (StringUtils.isEmpty(file)) {
                return getObject(this, key);
            }
            ConfigReader reader = map.computeIfAbsent(file, k -> {
                ConfigReader r = new ConfigReader();
                try {
                    String env = getDefault().getEnvFile(k);
                    if (env != null) {
                        try {
                            r.load(env);
                        } catch (IOException e) {
                            r.load(k);
                        }
                    } else {
                        r.load(k);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return r;
            });
            return getObject(reader, key);
        }

        public String getEnvFile(String name) {
            String env = getString(ENV);
            if (!StringUtils.isEmpty(env)) {
                int l = name.lastIndexOf('.');
                assert (l > 0);
                String prev = name.substring(0, l);
                String ext = name.substring(l);
                return prev + "-" + env + ext;
            }
            return null;
        }

        /**
         * 对于一个主配置类型来说,最主要的一个属性是env,表示当前的环境， 比如 env=test,那么在解析完 application.xx的主配置文件之后，
         * 会再去解析application-test.xxx文件,并把本配置文件的配置合并到主配置里面 注意是覆盖合并。
         */
        @Override
        public void load(String name) throws IOException {

            data.clear();
            map.clear();
            super.load(name);
            String envFile = getEnvFile(name);
            if (envFile != null) {
                super.load(envFile);
            }
        }

        protected Object getObject(ConfigReader reader, String key) {

            Object data = reader.get(key);
            if (data != null) {
                return data;
            }

            List<String> it = reader.keys(key + ".*");
            if (it.isEmpty()) {
                return null;
            }
            Map<String, Object> ret = new HashMap<>();
            for (String k : it) {
                ret.put(k.substring(key.length() + 1), reader.get(k));

            }
            return ret;
        }


    }

}
